﻿#region Copyright 2009 by Mike Podonyi, Licensed under the GNU Lesser General Public License
/*  This file is part of Console.Net.

 *  Console.Net is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.

 *  Console.Net is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.

 *  You should have received a copy of the GNU Lesser General Public License
 *  along with Console.Net.  If not, see <http://www.gnu.org/licenses/>.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Resources;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Reflection;
using System.IO;
using System.Xml;

namespace Empi.Consolenet
{
    internal class Parser
    {
        private readonly string[] Args;

        internal Parser(CommandDom rootCommand, string[] args)
        {
            RootCommand = rootCommand;
            
            Args = args;
        }


        private readonly CommandDom RootCommand;
        private bool ParserError = false;

        private void SendParserEvent(MessageTypes messageType, params string[] messageValues)
        {
            ParserError = true;
            throw new ConsoleParsingException(messageType, messageValues);
        }

        private bool ParseCommand(CommandDom xelement,  int index)
        {
            
            CommandDom command = (from i in ((IEnumerable<CommandDom>)xelement.Commands)
                                where i.Token.SplitMe().Contains(Args[index])
                                select i).SingleOrDefault();
            if (command != null)
            {
                //command.Choosen = true;
                GetCommand(command,  index);
                return true;
            }

            return false;
        }

        private void OccuranceValid(XElement xelement)
        {
            string maxOccur = null;
            if(xelement.Name.Equals(DataBucket.MainNameSpace+"option"))
                maxOccur = xelement.Element(DataBucket.MainNameSpace + "argument").Elements().First().Attribute("maxOccurs").Value;
                    //xelement.Attribute("maxOccurs").Value;
            else if(xelement.Name.Equals(DataBucket.MainNameSpace+"argument"))
                maxOccur = xelement.Elements().First().Attribute("maxOccurs").Value;

            var xbucket = xelement.Annotation<XElementBucket>();

            if (maxOccur.Equals("unbounded") || xbucket.Counter < int.Parse(maxOccur))
                return;
            else
                SendParserEvent(MessageTypes.MaxOccurance,xelement.Attribute("name").Value);
        }

        private class XElementBucket
        {
            internal int Counter = 0;
            internal bool ArgumentlessUsed = false;
            internal bool ArgumentUsed = false;
        }

        private bool ParseOption(CommandDom xelement,  int index)
        {
        
            
            IEnumerable<OptionDom> xoptions =
                (from i in ((IEnumerable<OptionDom>)xelement.Options)
                where i.Token.SplitMe().Any(s => Args[index].StartsWith(s))
                select i).ToArray();

            if(xoptions.Count()==0)
                return false;

            foreach (var xoption in xoptions)
            {
                var xbucket = xoption.Annotation<XElementBucket>();
                if (xbucket == null)
                    xoption.AddAnnotation(new XElementBucket());
            }

            

            var xoptionswithoutargument = (from i in xoptions
                                           where (i.Element.Element(DataBucket.MainNameSpace + "argument") == null || ((bool)i.Element.Element(DataBucket.MainNameSpace + "argument").Attribute("argumentOptional")))
                                           && i.Token.SplitMe().Contains(Args[index])
                                           select i).SingleOrDefault();

            if (xoptionswithoutargument!=null)
            {
                if (xoptionswithoutargument.Annotation<XElementBucket>().ArgumentUsed)
                    SendParserEvent(MessageTypes.ArgumentAlreadyUsed, xoptionswithoutargument.Name);

                if (xoptionswithoutargument.Annotation<XElementBucket>().ArgumentlessUsed)
                    SendParserEvent(MessageTypes.NoArgumentDoubleUsed, xoptionswithoutargument.Name);

                xoptionswithoutargument.Annotation<XElementBucket>().ArgumentlessUsed = true;

                xoptionswithoutargument.Annotation<XElementBucket>().Counter++;
                xoptionswithoutargument.Choosen = true; 
                return true;
            }
            
            //if (xoptionswithoutargument!=null)
            //{
            //    if (xoptionswithoutargument.Attribute("value").Value.SplitMe().Contains(Args[localindex]))
            //    {   // there can not be options with the same value but with subarguments. This is alreay forbidden by ValidateOptionArguments
            //        xoptionswithoutargument.AddAnnotation(true);
            //        return new Option() { Element = xoptionswithoutargument, Name = xoptionswithoutargument.Attribute("name").Value, Value = Args[localindex] };
            //    }
            //    //MP:return Message: Option on Commandline has argument but definition in xml file says only a option without argument is allowed
            //    return null;
            //}


            var xoptionswithstaticvalidator = from i in xoptions
                                           where i.Element.Element(DataBucket.MainNameSpace + "argument") != null &&
                                           i.Element.Element(DataBucket.MainNameSpace + "argument").Attribute("validatortype").Value == "static"
                                           select i;

            foreach (var xoption in xoptionswithstaticvalidator)
            {
                var f =
                    (from i in
                         (xoption.Element.Element(DataBucket.MainNameSpace + "argument").Elements(
                         DataBucket.MainNameSpace + "value").Attributes("value"))
                     from v in
                         xoption.Element.Element(DataBucket.MainNameSpace + "argument").Attribute("splitter").Value.
                         SplitMe()
                     from x in xoption.Token.SplitMe()
                     where (x + (v.Equals("''") ? string.Empty : v) + i.Value).Equals(Args[index])
                     select new
                                {
                                    Element = xoption,
                                    Value = x,
                                    ArgumentsValue = i.Value,
                                }).SingleOrDefault();

                if (f != null)
                {
                    if(xoption.Annotation<XElementBucket>().ArgumentlessUsed)
                        SendParserEvent(MessageTypes.NoArgumentAlreadyUsed, f.Element.Name, f.Value);

                    //OccuranceValid(f.Element);
                    if (xoption.Annotation<XElementBucket>().ArgumentUsed)
                        SendParserEvent(MessageTypes.OptionAlreadyUsed, f.Element.Name, f.Value, f.ArgumentsValue);

                    xoption.Annotation<XElementBucket>().ArgumentUsed = true;
                    xoption.Annotation<XElementBucket>().Counter++;

                    xoption.AddArgument(f.ArgumentsValue);
                    xoption.Choosen = true;
                    return true;
                }
            }

            var xoptionswithregexvalidator = from i in xoptions
                                              where i.Element.Element(DataBucket.MainNameSpace + "argument") != null &&
                                              i.Element.Element(DataBucket.MainNameSpace + "argument").Attribute("validatortype").Value == "regex"
                                             select i;
            
            foreach (var xElement in xoptionswithregexvalidator)
            {
                var f =
                    (
                     from v in xElement.Element.Element(DataBucket.MainNameSpace + "argument").Attribute("splitter").Value.SplitMe()
                     from x in xElement.Token.SplitMe()
                     let pre = x + (v.Equals("''") ? string.Empty : v)
                     where Args[index].StartsWith(pre)
                     let i = Args[index].Substring(pre.Length)
                     where Regex.IsMatch(i, xElement.Element.Element(DataBucket.MainNameSpace + "argument").Element(DataBucket.MainNameSpace + "regex").Attribute("value").Value)
                     select new
                     {
                         Element = xElement,
                         Value = x,
                         ArgumentsValue = i,
                     }).SingleOrDefault();  //MP:is it really singledefault ???

                if (f != null)
                {
                    if (xElement.Annotation<XElementBucket>().ArgumentlessUsed)
                        SendParserEvent(MessageTypes.NoArgumentAlreadyUsed, f.Element.Name, f.Value);

                    OccuranceValid(f.Element.Element);

                    xElement.Annotation<XElementBucket>().ArgumentUsed = true;
                    xElement.Annotation<XElementBucket>().Counter++;

                    xElement.Choosen = true;
                    xElement.AddArgument(f.ArgumentsValue);
                    return true;
                }
                
            }

            
            if (xoptionswithregexvalidator.Count() > 0 || xoptionswithstaticvalidator.Count()>0)  
                SendParserEvent(MessageTypes.OptionArgumentUnknown, Args[index]);

            return false;
        }

        private Type GetCommandType(XElement xelement)
        {
            //XAttribute handlerName = xelement.Attribute("handler");
            //if (handlerName != null)
            //{
            //    try
            //    {
            //        return Type.GetType(handlerName.Value);
            //    }
            //    catch 
            //    {
            //        throw;
            //    }
            //}
                
            return null;
        }

        

        private void GetCommand(CommandDom command,  int index)
        {
//            CommandBase val;

            //Type type = GetCommandType(command);
            //val = (type != null)
            //          ? Activator.CreateInstance(type) as CommandBase
            //          : new GenericCommand {Element = command, Token = ((index == 0) ? "root" : Args[index])};
            //command.Token = ((index == 0) ? "root" : Args[index]);

            //var locoptions = new List<Option>();
            //var locarguments = new List<Argument>();
            command.Choosen = true;

            bool hasargs = false, hasoptions = false;
            lasttokentype last=lasttokentype.none;
            for (index++; index < Args.Length && !ParserError; index++) 
            {
                //CLIToken tokenobject = null;
                if (ParseCommand(command,  index) && !ParserError)
                {
                    //val.ChoosenChildCommand = tokenobject as CommandBase;
                    break;
                }
                else if (ParseOption(command,  index) && !ParserError)
                {
                    if (hasoptions && last==lasttokentype.arg)
                        SendParserEvent(MessageTypes.ArgumentOrder, Args[index]);

                    //Option tokenoption = tokenobject as Option;
                    //var option = (from i in locoptions
                    //              where i.Name.Equals(tokenoption.Name)
                    //              select i).SingleOrDefault();
                    //if (option != null)
                    //    option.Arguments = option.Arguments.Concat(tokenoption.Arguments).ToArray();
                    //else
                    //    locoptions.Add(tokenobject as Option);

                    hasoptions = true;
                    last = lasttokentype.opt;
                }
                else if (ParseArgument(command,  index) && !ParserError)
                {   
                    if(hasargs && last==lasttokentype.opt)
                        SendParserEvent(MessageTypes.ArgumentOrder,Args[index]);

                    //locarguments.Add(tokenobject as Argument);

                    hasargs = true;
                    last = lasttokentype.arg;
                }
                else if(!ParserError)
                {
                    SendParserEvent(MessageTypes.UnknowToken, Args[index]);
                }
            }

            
            //val.Arguments = new ArgumentSet(locarguments);
            //val.Options = new OptionSet(locoptions);
            //ValidateOptions(val.Options);
        }

        private enum lasttokentype
        {
            none,
            arg,
            opt
        };

        private bool ParseArgument(CommandDom xelement,  int index)
        {
            
            ArgumentDom argument = (from i in ((IEnumerable<ArgumentDom>)xelement.Arguments)
                                     //xelement.Element.Elements(DataBucket.MainNameSpace + "arguments").Elements(DataBucket.MainNameSpace + "argument")
                                 select i).SingleOrDefault((xelem) =>
                                                               {
                                                                   switch (xelem.Element.Attribute("validatortype").Value)
                                                                   {
                                                                       case "regex":
                                                                           return Regex.IsMatch(Args[index],
                                                                                                xelem.Element.Element(DataBucket.MainNameSpace + "regex").
                                                                                                    Attribute("value").
                                                                                                    Value);
                                                                           
                                                                       default:
                                                                           return false;
                                                                   }
                                                               });

            if (argument != null)
            {
                var xbucket = argument.Annotation<XElementBucket>();
                if (xbucket == null)
                    argument.AddAnnotation(new XElementBucket());

                OccuranceValid(argument.Element);
                argument.Annotation<XElementBucket>().Counter++;
                Debug.Assert(!string.IsNullOrEmpty(argument.Name));

                argument.AddArgument(Args[index]);
                
                return true;
            }

           
             

            return false;
        }

        internal void Parse()
        {
            int index = 0;
            
            GetCommand(RootCommand,  index);
                //.XCLIStructure.Element(DataBucket.MainNameSpace+"console.net").Element(DataBucket.MainNameSpace+"command"), ref index);
        }

        //private void ValidateOptions(IEnumerable<IOption> options)
        //{
        //    IEnumerable<string> optionnames = from i in options
        //                                      select i.Name;

        //    foreach (Option option in options)
        //    {
        //        foreach (XElement excludeoptionset in option.Element.Elements(DataBucket.MainNameSpace + "excludingsets").Elements(DataBucket.MainNameSpace + "excludingset"))
        //        {
        //            IEnumerable<string> excludeoptions =
        //                excludeoptionset.Elements(DataBucket.MainNameSpace + "option").Attributes("name").Select((attr) => attr.Value);

        //            if (excludeoptions.Count() == excludeoptions.Intersect(optionnames).Count())
        //            {
        //                SendParserEvent(MessageTypes.ExcludeOption,option.Name, string.Join(",", excludeoptions.ToArray()));
        //            }
        //        }
        //    }
        //}
    }
}